package both.map.mapModel;

import java.awt.Dimension;
import both.map.*;
/**
 * The individual layers that go into chunks. Layers are given a type when they 
 * are created (the type comes from the LayerType enum), and this type governs
 * what type of cell 
 * 
 * @author jdl
 *
 */
public class ModelLayer {
	private Dimension size;
	private final LayerType type;
	private ModelCell[][] cells;
	private boolean partOfUpdate;
	
	/**
	 * Construct a new layer
	 * @param type the type of this layer
	 * @param size the dimensions of this layer in cells
	 */
	public ModelLayer(LayerType type, Dimension size)
	{
		this(type, size, false);
	}
	
	/**
	 * construct a new layer, specifying whether or not it is part of an update
	 * @param size
	 */
	private ModelLayer(LayerType type, Dimension size, boolean partOfUpdate)
	{
		this.type = type;
		this.size = size;
		this.partOfUpdate = partOfUpdate;
		makeCells(size);
		
	}
	
	private void makeCells(Dimension size)
	{
		cells = new ModelCell[size.width][size.height];
		
		for (int x=0; x<size.width; x++) {
			for (int y=0; y<size.height; y++) {
				ModelCell content;
				switch (type) {
					case TERRAIN:
						content = new TerrainCell();
						break;
					case VISIBILITY:
						content = new VisibilityCell();
						break;
					case TOKEN:
						content = new TokenCell();
						break;
					default :
						content = new TokenCell();
						break;
				}
				cells[x][y] = content;
			}
		}
	}
	
	/**
	 * mask a cell as part of an update
	 * @param x
	 * @param y
	 * @throws UpdateStatusException if this layer is not part
	 * of an update
	 */
	public void maskCell(int x, int y)
		throws UpdateStatusException
	{
		if (!partOfUpdate) {
			throw new UpdateStatusException("[" + x + "," + y + "] - " +
											toString() + " - Cannot mask a cell" +
											" which is not part of an update.");
		}
		cells[x][y] = null;
	}
	
	/**
	 * Mark an updated cell to be stored, rather than passed. Used as part
	 * of masking for visibility. A cell which has a stored update keeps its
	 * "updated" flag, even though the update has been passed. It is left up to
	 * client classes when they want updates stored, so clients should use
	 * this method responsibly.
	 * TODO error checking
	 * TODO responsibility for accurate update storing in this class and cells,
	 * instead of client classes
	 * @param x
	 * @param y
	 */
	public void storeUpdate(int x, int y)
	{
		cells[x][y].storeUpdate();
	}
	
	/**
	 * @return the dimensions of this layer
	 */
	public Dimension getSize()
	{
		return size;
	}
	
	/**
	 * get a cell from a given location
	 * @param xPos the x position of the desired cell
	 * @param yPos the y position of the desired cell
	 * @return the desired cell
	 * @throws ArrayIndexOutOfBoundsException if the position is out of bounds
	 */
	public ModelCell getCell(int xPos, int yPos)
	{
		checkPos(xPos, yPos);
		
		return cells[xPos][yPos];
	}
	
	/**
	 * check a given set of coordinates to see whether they are in bounds, and 
	 * throw an exception if not
	 */
	private void checkPos(int xPos, int yPos)
	{
		if (xPos < 0 || yPos < 0 || xPos >= size.width || yPos >= size.height) {
			throw new ArrayIndexOutOfBoundsException("desired cell location does not exist ("
														+ xPos + "," + yPos + ").");
		}
	}
	
	/**
	 * gives ModelLayer with all cells which have an update flag
	 * @return the list of cells
	 */
	public ModelLayer grabUpdate()
	{
		ModelLayer update = new ModelLayer(type, size, true);
		boolean atLeastOneUpdate = false;
		for (int j=0; j<size.height; j++) {
			for (int i=0; i<size.width; i++) {
				ModelCell updateCell = cells[i][j].grabUpdate();
				if (updateCell == null) {
					try {
						update.maskCell(i,j);
					} catch (UpdateStatusException e) {
						System.out.println(e);
					}
				} else {
					atLeastOneUpdate = true;
					update.getCell(i,j).applyUpdate(updateCell);
				}
			}
		}
		if (atLeastOneUpdate) {
			return update;
		}
		else {
			return null;
		}
	}
	
	/**
	 * apply an update to this layer
	 * @param updateLayer the layer to be used in updating this one
	 * @throws IllegalArgumentException if the updateLayer does not match this one
	 */
	public void applyUpdate(ModelLayer updateLayer)
	{
		if (updateLayer != null) {
			if (! updateLayer.getSize().equals(this.getSize())) {
				throw new IllegalArgumentException("UpdateLayer Dim: " + updateLayer.getSize() +
												"\nattempted to update layer dim: " +
												this.getSize());
			}
			if (! updateLayer.getType().equals(this.getType())) {
				throw new IllegalArgumentException("UpdateLayer type: " + updateLayer.getType() +
												"\nattempted to update layer type: " +
												this.getType());
			}
			for (int x=0; x<size.width; x++) {
				for (int y=0; y<size.height; y++) {
					ModelCell updateCell = updateLayer.getCell(x,y);
					cells[x][y].applyUpdate(updateCell);
				}
			}
		}
	}
	
	/**
	 * @return whether this cell is part of an update
	 */
	public boolean isPartOfUpdate()
	{
		return partOfUpdate;
	}
	
	/**
	 * @return the type of this layer
	 */
	public LayerType getType()
	{
		return type;
	}
}
